﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace 物品查看器
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private float _AssemblerSpeed = 1;
        public float AssemblerSpeed
        {
            get => _AssemblerSpeed;
            set
            {
                _AssemblerSpeed = value;
                NotifyChange(new PropertyChangedEventArgs("AssemblerSpeed"));
            }
        }

        public AllItemViewModel RecipeItems { get; set; } = new AllItemViewModel();

        private Dictionary<string, RecipeItem> RecipeItemDict = new Dictionary<string, RecipeItem>();

        public event PropertyChangedEventHandler PropertyChanged;

        private ProfileViewModel _ProfileVM = new ProfileViewModel();
        public ProfileViewModel ProfileVM
        {
            get => _ProfileVM;
            set
            {
                _ProfileVM = value;
                NotifyChange(new PropertyChangedEventArgs("ProfileVM"));
            }
        }

        private RecipeItem _SelectedRecipeItem;
        public RecipeItem SelectedRecipeItem { 
            get { return _SelectedRecipeItem; } 
            set 
            { 
                _SelectedRecipeItem = value; 
                NotifyChange(new PropertyChangedEventArgs("SelectedRecipeItem")); 
            } 
        }
        public ObservableCollection<object> TabViews { get; set; } = new ObservableCollection<object>();

        private Point PrevPos;
        private bool NetMouseDown;

        public MainWindow()
        {
            InitializeComponent();

            DataContext = this;

            var assembly = Assembly.GetExecutingAssembly();
            string[] resourceName = assembly.GetManifestResourceNames();

            StreamReader sr = new StreamReader(assembly.GetManifestResourceStream("物品查看器.downloadrecipe.json"));
            string json = sr.ReadToEnd();
            var datas = new { items = new List<GameElement>() };
            List<GameElement> gameElements = JsonConvert.DeserializeAnonymousType(json, datas).items;

            foreach(GameElement gameElement in gameElements)
            {
                RecipeItem recipeItem = new RecipeItem(0, gameElement);
                RecipeItems.Add(recipeItem);
                RecipeItemDict.Add(recipeItem.Name, recipeItem);
            }

            RecipeItem.PrepareBeltsAndPlants(RecipeItems.ToList<RecipeItem>());

            RecipeItem.PrepareRecipes(RecipeItems.ToList<RecipeItem>(), RecipeItemDict, gameElements);

            TabViews.Add(RecipeItems);
            TabViews.Add(ProfileVM);

            ProfileVM.SelectedItem = RecipeItems[32];
            TabViews.Add(RecipeItems[22]);
            (TabViews[2] as RecipeItem).GenerateNetView(120);

            tabControl.SelectedIndex = 0;
        }

        private void NotifyChange(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }

        private string getImagePath(string imageName)
        {
            return "resource/" + imageName;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Button btn = sender as Button;
            //Console.WriteLine(btn.Tag);
            int index = (int)btn.Tag;
            tabControl.SelectedIndex = 1;

            ProfileVM.SelectedItem = RecipeItems[index];
            ProfileVM.SelectedItem.ReGenerateView(AssemblerSpeed);
        }

        private void Checked(object sender, RoutedEventArgs e)
        {
            if (ProfileVM.SelectedItem != null)
            {
                ProfileVM.SelectedItem.ReGenerateView(AssemblerSpeed);
                ProfileVM.NotifyChanged();
            }
        }

        public Type Type { get { return this.GetType(); } }


        private void Button_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
        {
            Button btn = sender as Button;
            RecipeItem item;

            if (btn.Tag is string)
            {
                item = RecipeItemDict[btn.Tag as string];
            }
            else
            {
                int index = (int)btn.Tag;
                item = RecipeItems[index];
            }

            item.GenerateNetView(120);
            if (!TabViews.Contains(item))
                TabViews.Add(item);
        }

        private void InfoButton_Click(object sender, RoutedEventArgs e)
        {
            Button btn = sender as Button;
            string name = btn.Tag as string;
            ProfileVM.SelectedItem = RecipeItemDict[name];
            ProfileVM.SelectedItem.ReGenerateView(AssemblerSpeed);
        }

        private void NetPlantButton_Click(object sender, RoutedEventArgs e)
        {
            Button btn = sender as Button;

            NetItemView view = btn.DataContext as NetItemView;
            RecipeItem item = view.Item;

            ItemFormula formula = item.Formulas[item.FormulaIndex];
            RecipeItem selectItem = TabViews[tabControl.SelectedIndex] as RecipeItem;
            item.Formulas[item.FormulaIndex].SwitchPlant();
            if (Keyboard.GetKeyStates(Key.LeftShift)!=KeyStates.Down)
            {
                foreach(RecipeItem i in RecipeItems)
                {
                    foreach(ItemFormula f in i.Formulas)
                    {
                        if (f.PlantName.Equals(formula.PlantName))
                        {
                            f.PlantIndex = formula.PlantIndex;
                        }
                    }
                }
            }
            selectItem.GenerateNetView();
        }

        private void RecipeNet_MouseMove(object sender, MouseEventArgs e)
        {
            if (!NetMouseDown)
                return;

            Point pos = Mouse.GetPosition(null);

            double dx = pos.X - PrevPos.X;
            double dy =  pos.Y -PrevPos.Y;

            Point offset = ((sender as DockPanel).DataContext as RecipeItem).NetOffset;
            offset.X += dx;
            offset.Y += dy;
            ((sender as DockPanel).DataContext as RecipeItem).NetOffset = offset;

            PrevPos = pos;
        }

        private void RecipeNet_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            PrevPos = Mouse.GetPosition(null);
            NetMouseDown = true;
        }

        private void RecipeNet_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            NetMouseDown = false; 
        }
        private void RecipeNet_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            RecipeItem recipe = (sender as DockPanel).DataContext as RecipeItem;
            recipe.RecipeSize += e.Delta > 0 ? 5 : -5;
            Console.WriteLine(recipe.RecipeSize.ToString() + ' ' + e.Delta.ToString());
        }

        private void Quantity_KeyDown(object sender, KeyEventArgs e)
        {
            TextBox tb = sender as TextBox;
            RecipeItem item = tb.DataContext as RecipeItem;

            if (item == null)
                return;

            if (e.Key != Key.Enter)
                return;

            float quantity = item.Quantity;
            try
            {
                quantity = float.Parse(tb.Text);
            }
            catch (Exception exce)
            {
                Console.WriteLine(exce.Message);
            }
            _ = item.GenerateNetView(quantity);
        }

        private void TabClose_Click(object sender, RoutedEventArgs e)
        {
            Button btn = sender as Button;
            RecipeItem item = btn.Tag as RecipeItem;
            TabViews.Remove(item);
        }
    }

    public class YieldConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value.ToString() + "/m";
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public class ItemWidthConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            double result = (double)values[0] * (int)values[1];
            return double.Parse(parameter as string) * (int)values[1];
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public class GameElement
    {
        public string name { get; set;  }
        public string image { get; set; }
        public string[] type { get; set; }
        public List<Recipe> from { get; set; }

    }
    public class Recipe
    {
        public Dictionary<string, int> matter { get; set; }
        public float cost { get; set; }
        public string plant { get; set; }
        public Dictionary<string, int> product { get; set; }
    }

    public class RecipeItem: INotifyPropertyChanged
    {
        public string Name { get; set; }
        public float Quantity { get; set; }
        public string ItemImagePath { get; set; }
        public int FormulaIndex { get; set; } = 0;
        public List<ItemFormula> Formulas { get; set; } = new List<ItemFormula>();
        public List<ItemFormula> AdditionFormulas { get; set; } = new List<ItemFormula>();
        public static Dictionary<string, List<RecipeItem>> Plants{get;set;}
        public int BeltIndex { get; set; } = 0;
        public static List<RecipeItem> Belts{get;set;}

        public ObservableCollection<NetItemView> NetView { get; set; } = new ObservableCollection<NetItemView>();
        private Point _NetOffset = new Point(0, 0);
        public Point NetOffset {
            get
            {
                return _NetOffset;
            }
            set
            {
                _NetOffset = value;
                NotifyChange(new PropertyChangedEventArgs("NetOffset"));
            }
        }

        private double _RecipeSize = 80;
        public double RecipeSize
        {
            get
            {
                return _RecipeSize;
            }
            set
            {
                _RecipeSize = value;
                NotifyChange(new PropertyChangedEventArgs("RecipeSize"));
            }
        }

        public ObservableCollection<NetItemView> TotalConsume { get; set; } = new ObservableCollection<NetItemView>();
        public ObservableCollection<NetItemView> TotalPlant { get; set; } = new ObservableCollection<NetItemView>();

        public RecipeItem()
        {

        }


        public RecipeItem (float quantity, GameElement element)
        {
            Quantity = quantity;
            ItemImagePath = "Resource/"+element.image;
            Name = element.name;

        }
        public Type Type { get { return this.GetType(); } }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyChange(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }

        public static void PrepareBeltsAndPlants(List<RecipeItem> recipeItems)
        {
            string[] plantNames = new string[]
            {
                "制造台",
                "冶炼设备",
                "精炼设备",
                "化工设备",
                "粒子对撞机",
                "能量枢纽",
                "分馏设备",
                "电网",
                "科研设备"
            };
            Plants = new Dictionary<string, List<RecipeItem>>();
            foreach (string name in plantNames)
            {
                Plants.Add(name, new List<RecipeItem>());
            }

            Belts = new List<RecipeItem>();

            foreach(RecipeItem item in recipeItems)
            {
                
                if (item.Name.Contains("制造台"))
                {
                    Plants["制造台"].Add(item);
                }
                else if (item.Name.Contains("熔炉"))
                {
                    Plants["冶炼设备"].Add(item);
                }
                else if (item.Name.Contains("化工厂"))
                {
                    Plants["化工设备"].Add(item);
                }
                else if (item.Name.Contains("原油精炼厂"))
                {
                    Plants["精炼设备"].Add(item);
                }
                else if (item.Name.Contains("能量枢纽"))
                {
                    Plants["能量枢纽"].Add(item);
                }
                else if (item.Name.Contains("粒子对撞机"))
                {
                    Plants["粒子对撞机"].Add(item);
                }
                else if (item.Name.Contains("研究站"))
                {
                    Plants["科研设备"].Add(item);
                }
                else if (item.Name.Contains("传送带"))
                {
                    Belts.Add(item);
                }
                else if (item.Name.Contains("分馏塔"))
                {
                    Plants["分馏设备"].Add(item);
                }
                else if (item.Name.Contains("人造恒星"))
                {
                    Plants["电网"].Add(item);
                }
            }
        }

        public static void PrepareRecipes(List<RecipeItem> recipeItems, Dictionary<string, RecipeItem> pairs, List<GameElement> elements)
        {
            for (int i=0; i<recipeItems.Count; i++)
            {
                RecipeItem item = recipeItems[i];
                GameElement element = elements[i];

                if (element.from == null)
                    continue;

                foreach(Recipe recipe in element.from)
                {
                    ItemFormula formula = new ItemFormula();
                    formula.Cost = recipe.cost;
                    formula.PlantName = recipe.plant;

                    foreach(KeyValuePair<string, int> pair in recipe.matter)
                    {
                        formula.Materials.Add(pairs[pair.Key], pair.Value);
                    }

                    foreach(KeyValuePair<string, int> pair in recipe.product)
                    {
                        formula.Products.Add(pairs[pair.Key], pair.Value);
                    }

                    formula.GenerateView();

                    item.Formulas.Add(formula);

                    foreach(KeyValuePair<RecipeItem, int> pair in formula.Materials)
                    {
                        pair.Key.AdditionFormulas.Add(formula);
                    }
                }

            }
        }

        public void ReGenerateView(float assemblerSpeed)
        {
            foreach(ItemFormula formula in Formulas)
            {
                if (!formula.PlantName.Equals("制造台"))
                    continue;
                foreach(FormulaItemView view in formula.MaterialsView)
                {
                    view.CalcBeltLoads(assemblerSpeed);
                }
                foreach(FormulaItemView view in formula.ProductsView)
                {
                    view.CalcBeltLoads(assemblerSpeed);
                }
            }

            foreach(ItemFormula formula in AdditionFormulas)
            {
                if (!formula.PlantName.Equals("制造台"))
                    continue;
                foreach(FormulaItemView view in formula.MaterialsView)
                {
                    view.CalcBeltLoads(assemblerSpeed);
                }
                foreach(FormulaItemView view in formula.ProductsView)
                {
                    view.CalcBeltLoads(assemblerSpeed);
                }
            }
        }

        public NetItemView GenerateNetItemView(RecipeItem item, float yield, int row, ref int col)
        {

            NetItemView itemView = new NetItemView();
            itemView.ImagePath = ItemImagePath;
            itemView.Item = this;
            itemView.PlantRequire = 0;
            itemView.Row = row;
            itemView.Col = col;
            itemView.Yield = yield;
            itemView.Width = 1;
            itemView.PlantPath = ItemImagePath;

            if (Formulas.Count > 0)
            {
                ItemFormula formula = Formulas[FormulaIndex];
                int itemCount = formula.Products[this];
                itemView.PlantRequire = yield * formula.Cost / itemCount / 60/formula.GetPlantSpeed();
                itemView.PlantPath = Plants[formula.PlantName][formula.PlantIndex].ItemImagePath;
                RecipeItem lastMaterial = formula.Materials.Keys.Last();
                foreach (KeyValuePair<RecipeItem, int> pair in formula.Materials)
                {
                    itemView.ChildCount++;
                    float subitemYield = yield * pair.Value / itemCount;
                    NetItemView v = pair.Key.GenerateNetItemView(item, subitemYield, row + 1, ref col);
                    itemView.Children.Add(v);
                    col++;
                    if (pair.Key.Equals(lastMaterial))
                        col--;

                }
            }
            item.NetView.Add(itemView);

            NetItemView iv = item.TotalConsume.ToList().Find(e => e.ImagePath.Equals(itemView.ImagePath));
            if (iv != null)
            {
                iv.PlantRequire += itemView.PlantRequire;
                iv.Yield += itemView.Yield;
            }
            else
            {
                iv = new NetItemView();
                iv.PlantRequire = itemView.PlantRequire;
                iv.PlantPath = itemView.PlantPath;
                iv.Yield = itemView.Yield;
                iv.Item = itemView.Item;
                iv.ImagePath = itemView.ImagePath;
                item.TotalConsume.Add(iv);
            }

            iv = item.TotalPlant.ToList().Find(e => e.ImagePath.Equals(itemView.PlantPath));
            if (iv != null)
            {
                iv.Yield += itemView.PlantRequire;
            }
            else if (!itemView.ImagePath.Equals(itemView.PlantPath))
            {
                iv = new NetItemView
                {
                    ImagePath = itemView.PlantPath,
                    Yield = itemView.PlantRequire
                };
                item.TotalPlant.Add(iv);
            }

            return itemView;
        }

        public ObservableCollection<NetItemView> GenerateNetView(float yield)
        {
            Quantity = yield;

            return GenerateNetView();
        }

        public ObservableCollection<NetItemView> GenerateNetView()
        {
            NetView.Clear();
            TotalConsume.Clear();
            TotalPlant.Clear();

            int col = 0;
            GenerateNetItemView(this, Quantity, 0, ref col);
            return NetView;
        }
    }

    public class ItemFormula
    {
        public Dictionary<RecipeItem, int> Materials { get; set; } = new Dictionary<RecipeItem, int>();
        public Dictionary<RecipeItem, int> Products { get; set; } = new Dictionary<RecipeItem, int>();
        public float Cost{get;set;}
        public string PlantName{get;set;}
        public int PlantIndex { get; set; }

        public List<FormulaItemView> MaterialsView { get; set; } = new List<FormulaItemView>();
        public List<FormulaItemView> ProductsView { get; set; } = new List<FormulaItemView>();


        public ItemFormula()
        {


        }

        public void GenerateView()
        {
            foreach(KeyValuePair<RecipeItem, int> pair in Materials)
            {
                FormulaItemView view = new FormulaItemView();
                view.ImagePath = pair.Key.ItemImagePath;
                view.Name = pair.Key.Name;
                view.Count = pair.Value;
                view.Cost = Cost;
                view.CalcBeltLoads(1);
                MaterialsView.Add(view);
            }
            
            foreach(KeyValuePair<RecipeItem, int> pair in Products)
            {
                FormulaItemView view = new FormulaItemView();
                view.ImagePath = pair.Key.ItemImagePath;
                view.Count = pair.Value;
                view.Cost = Cost;
                view.Name = pair.Key.Name;
                view.CalcBeltLoads(1);
                ProductsView.Add(view);
            }
        }

        public void SwitchPlant()
        {
            if (PlantName.Equals("制造台"))
            {
                if (PlantIndex < 2)
                    PlantIndex++;
                else
                    PlantIndex = 0;
            }
            else if (PlantName.Equals("冶炼设备"))
            {
                if (PlantIndex < 1)
                    PlantIndex++;
                else
                    PlantIndex = 0;
            }
        }

        public float[] GetPlantSpeeds()
        {
            if (PlantName.Equals("制造台"))
            {
                float[] plantSpeeds = new float[] { 0.75f, 1, 1.5f};
                return plantSpeeds;
            }
            else if (PlantName.Equals("冶炼设备"))
            {
                float[] plantSpeeds = new float[] { 1, 2 };
                return plantSpeeds;
            }

            return new float[] { 1 };
        }

        public float GetPlantSpeed()
        {
            return GetPlantSpeeds()[PlantIndex];
        }
    }

    public class FormulaItemView
    {
        public string ImagePath { get; set; }
        public float Cost { get; set; }
        public float[] BeltLoads { get; set; }
        public int Count { get; set; }
        public string Name { get; set; }

        public void CalcBeltLoads(float assemblerSpeed)
        {
            float[] beltSpeeds = new float[3] { 6, 12, 30 };
            BeltLoads = new float[3];

            for (int i=0; i<3; i++)
            {
                BeltLoads[i] = beltSpeeds[i] * Cost/assemblerSpeed / Count;
            }
        }
    }

    public class NetItemView:INotifyPropertyChanged
    {
        public string ImagePath { get; set; }
        private string _PlantPath;
        public string PlantPath {
            get => _PlantPath;
            set
            {
                _PlantPath = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("PlantPath"));
            }
        }
        public float Yield { get; set; }
        public float PlantRequire { get; set; }
        public RecipeItem Item { get; set; }
        public int Col { get; set; }
        public int Row { get; set; }
        public int Width { get; set; }
        public int ChildCount { get; set; } = 0;
        public List<NetItemView> Children = new List<NetItemView>();

        public event PropertyChangedEventHandler PropertyChanged;
    }

    public class OriginItemMockData
    {
        public List<RecipeItem> RecipeItems { get; set; } = new List<RecipeItem>();
        public OriginItemMockData()
        {
            RecipeItems.Add(new RecipeItem() { ItemImagePath = "resource/assembler-1.png" });
            RecipeItems.Add(new RecipeItem() { ItemImagePath = "resource/assembler-2.png" });
        }
    }

    public class MergeFormulasConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            List<ItemFormula> formulas0 = values[0] as List<ItemFormula>;
            List<ItemFormula> formulas1 = values[1] as List<ItemFormula>;
            if (formulas1 == null || formulas0 == null)
                return null;
            return formulas0.Union(formulas1);
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public class NetItemWidthConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            double height = (double)values[0];
            int childCount = (int)values[1];

            return childCount==0?100:100.0 * childCount;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public class AllItemViewModel : ObservableCollection<RecipeItem>
    {
    }

    public class ProfileViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private RecipeItem _SelectedItem;
        public RecipeItem SelectedItem
        {
            get => _SelectedItem;
            set
            {
                _SelectedItem = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SelectedItem"));
            }
        }

        public void NotifyChanged()
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SelectedItem"));
        }
    }

    public class TabContentTemplateSelector:DataTemplateSelector
    {
        public DataTemplate AllItem { get; set; }
        public DataTemplate Profile { get; set; }
        public DataTemplate General { get; set; }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            if (item is RecipeItem)
            {
                return General;
            }
            else if (item is ProfileViewModel)
            {
                return Profile;
            }
            else
            {
                return AllItem;
            }
        }
    }

    public class TabHeaderTemplateSelector : DataTemplateSelector
    {
        public DataTemplate AllItem { get; set; }
        public DataTemplate Profile { get; set; }
        public DataTemplate General { get; set; }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            if (item is RecipeItem)
            {
                return General;
            }
            else if (item is ProfileViewModel)
            {
                return Profile;
            }
            else
            {
                return AllItem;
            }
        }
    }
    
}
